#include <cppunit/config/SourcePrefix.h>

#include "PKBStubForFollowTest.h"
#include "PKBStubForParentTest.h"
#include "QueryProcessorWrapper.h"
#include "QueryExpressionParser.h"
#include "TestQueryProcessorWrapper.h"


void QueryProcessorWrapperTest::setUp() {
	cout << "Test QueryProcessorWrapper setUp()\n";
}

void QueryProcessorWrapperTest::tearDown() {
}

// Registers the fixture into the 'registry'
CPPUNIT_TEST_SUITE_REGISTRATION( QueryProcessorWrapperTest );

// method to test the constructor
void QueryProcessorWrapperTest::testConstructor() {
	cout << "Test QueryProcessorWrapper testConstructor()\n";
	// Just to ensure it does not produce errors
	PKB *pkb = PKB::getInstance();
	QueryExpressionParser *queryExpParser = new QueryExpressionParser(pkb);
	QueryPreprocessor *preprocessor = new QueryPreprocessor(queryExpParser);
	QueryEvaluator *evaluator = new QueryEvaluator(pkb);
	QueryProjector *projector = new QueryProjector();
	QueryProcessorWrapper queryProcessor = QueryProcessorWrapper(preprocessor, evaluator, projector);
}

// Tests the evaluation of the query, just to make sure that it can run
// without error (whether it returns the correct result or not is not tested)
void QueryProcessorWrapperTest::testEvaluateQueryRunnable() {
	cout << "Test QueryProcessorWrapper evaluate query if it is runnable\n";
	PKBStubForFollowTest pkbStubForFollows;
	QueryExpressionParser *queryExpParser = new QueryExpressionParser(&pkbStubForFollows);
	QueryPreprocessor preprocessor = QueryPreprocessor(queryExpParser);
	QueryEvaluator evaluator = QueryEvaluator(&pkbStubForFollows);
	QueryProjector projector = QueryProjector();
	QueryProcessorWrapper queryProcessor = QueryProcessorWrapper(&preprocessor, &evaluator, &projector);
	std::vector<std::string> results;
	
	results = queryProcessor.evaluateQuery("assign a; Select a such that Follows(a, 2)");
	CPPUNIT_ASSERT_EQUAL(1, (int)results.size());

	results = queryProcessor.evaluateQuery("assign a; stmt s; Select a such that Follows(s, 2)");
	CPPUNIT_ASSERT_EQUAL(4, (int)results.size());

	results = queryProcessor.evaluateQuery("assign a; stmt s; Select a such that Follows(1, 2)");
	CPPUNIT_ASSERT_EQUAL(4, (int)results.size());
}

// Test if query evaluator can handle invalid query input
void QueryProcessorWrapperTest::testEvaluateInvalidQuery() {
	cout << "Test QueryProcessorWrapper evaluating invalid query\n";
	PKBStubForFollowTest pkbStubForFollows;
	QueryExpressionParser *queryExpParser = new QueryExpressionParser(&pkbStubForFollows);
	QueryPreprocessor preprocessor = QueryPreprocessor(queryExpParser);
	QueryEvaluator evaluator = QueryEvaluator(&pkbStubForFollows);
	QueryProjector projector = QueryProjector();
	QueryProcessorWrapper queryProcessor = QueryProcessorWrapper(&preprocessor, &evaluator, &projector);
	std::vector<std::string> results;
	
	results = queryProcessor.evaluateQuery("assign a; Select a such that Follows(ab, 2)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());
}

// Test if Follows relationship can be evaluated correctly.
void QueryProcessorWrapperTest::testEvaluateFollowsRelationship() {
	cout << "Test QueryprocessorWrapper evaluate follows\n";
	PKBStubForFollowTest pkbStubForFollows;
	QueryExpressionParser *queryExpParser = new QueryExpressionParser(&pkbStubForFollows);
	QueryPreprocessor preprocessor = QueryPreprocessor(queryExpParser);
	QueryEvaluator evaluator = QueryEvaluator(&pkbStubForFollows);
	QueryProjector projector = QueryProjector();
	QueryProcessorWrapper queryProcessor = QueryProcessorWrapper(&preprocessor, &evaluator, &projector);
	std::vector<std::string> results;
	

	// Different synonym declaration
	results = queryProcessor.evaluateQuery("variable a; Select a such that Follows(a, 2)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	results = queryProcessor.evaluateQuery("stmt s; Select s such that Follows(s, 2)");
	CPPUNIT_ASSERT_EQUAL(1, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("1"), 0);

	results = queryProcessor.evaluateQuery("while w; Select w such that Follows(w, 2)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	results = queryProcessor.evaluateQuery("constant c; Select c such that Follows(c, 2)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	// More than one synonym declaration
	results = queryProcessor.evaluateQuery("assign a; stmt s; Select s such that Follows(s, 2)");
	CPPUNIT_ASSERT_EQUAL(1, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("1"), 0);

	results = queryProcessor.evaluateQuery("assign a, a1; stmt s1, s; Select s such that Follows(s, 2)");
	CPPUNIT_ASSERT_EQUAL(1, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("1"), 0);
	

	// Edge cases in Follows Relationship -- stmt# provided is not used in the programme
	results = queryProcessor.evaluateQuery("stmt s; Select s such that Follows(s, 5)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	results = queryProcessor.evaluateQuery("stmt s; Select s such that Follows(s, 0)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	results = queryProcessor.evaluateQuery("stmt s; Select s such that Follows(s, -1)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	/* Throws index out of range error
	results = queryProcessor.evaluateQuery("stmt s; Select s such that Follows(s, 12332321321)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());
	*/

	// Query result is independent of the relationship
	results = queryProcessor.evaluateQuery("assign a, a1; stmt s1, s; Select a such that Follows(1, 2)");
	CPPUNIT_ASSERT_EQUAL(4, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("1"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(1).compare("2"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(2).compare("3"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(3).compare("4"), 0);

	results = queryProcessor.evaluateQuery("assign a, a1; stmt s1, s; Select a such that Follows(4, 3)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());
}

// Test if Follows* relationship can be evaluated correctly.
void QueryProcessorWrapperTest::testEvaluateFollowsStarRelationship() {
	cout << "Test QueryprocessorWrapper evaluate follows star\n";
	PKBStubForFollowTest pkbStubForFollows;
	QueryExpressionParser *queryExpParser = new QueryExpressionParser(&pkbStubForFollows);
	QueryPreprocessor preprocessor = QueryPreprocessor(queryExpParser);
	QueryEvaluator evaluator = QueryEvaluator(&pkbStubForFollows);
	QueryProjector projector = QueryProjector();
	QueryProcessorWrapper queryProcessor = QueryProcessorWrapper(&preprocessor, &evaluator, &projector);
	std::vector<std::string> results;
	

	// Different synonym declaration
	results = queryProcessor.evaluateQuery("variable a; Select a such that Follows*(a, 2)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	results = queryProcessor.evaluateQuery("stmt s; Select s such that Follows*(s, 4)");
	CPPUNIT_ASSERT_EQUAL(3, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("1"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(1).compare("2"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(2).compare("3"), 0);

	results = queryProcessor.evaluateQuery("while w; Select w such that Follows*(w, 2)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	results = queryProcessor.evaluateQuery("constant c; Select c such that Follows*(c, 2)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	// More than one synonym declaration
	results = queryProcessor.evaluateQuery("assign a; stmt s; Select s such that Follows*(s, 2)");
	CPPUNIT_ASSERT_EQUAL(1, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("1"), 0);

	results = queryProcessor.evaluateQuery("assign a, a1; stmt s1, s; Select s such that Follows*(s, 2)");
	CPPUNIT_ASSERT_EQUAL(1, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("1"), 0);
	

	// Edge cases in Follows* Relationship -- stmt# provided is not used in the programme
	results = queryProcessor.evaluateQuery("stmt s; Select s such that Follows*(s, 5)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	results = queryProcessor.evaluateQuery("stmt s; Select s such that Follows*(s, 0)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	results = queryProcessor.evaluateQuery("stmt s; Select s such that Follows*(s, -1)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());


	// RelArgs are both synonyms
	results = queryProcessor.evaluateQuery("stmt s; while w; Select s such that Follows*(w, s)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	results = queryProcessor.evaluateQuery("stmt s; assign a; Select s such that Follows*(a, s)");
	CPPUNIT_ASSERT_EQUAL(3, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("2"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(1).compare("3"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(2).compare("4"), 0);


	// Query result is independent of the relationship
	results = queryProcessor.evaluateQuery("assign a, a1; stmt s1, s; Select a such that Follows*(1, 2)");
	CPPUNIT_ASSERT_EQUAL(4, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("1"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(1).compare("2"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(2).compare("3"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(3).compare("4"), 0);

	results = queryProcessor.evaluateQuery("assign a, a1; stmt s1, s; Select a such that Follows*(1, 3)");
	CPPUNIT_ASSERT_EQUAL(4, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("1"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(1).compare("2"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(2).compare("3"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(3).compare("4"), 0);
}

// Test if Parent relationship can be evaluated correctly
void QueryProcessorWrapperTest::testEvaluateParentRelationship() {
	cout << "Test QueryprocessorWrapper evaluate parent\n";
	PKBStubForParentTest pkbStubForParent;
	QueryExpressionParser *queryExpParser = new QueryExpressionParser(&pkbStubForParent);
	QueryPreprocessor preprocessor = QueryPreprocessor(queryExpParser);
	QueryEvaluator evaluator = QueryEvaluator(&pkbStubForParent);
	QueryProjector projector = QueryProjector();
	QueryProcessorWrapper queryProcessor = QueryProcessorWrapper(&preprocessor, &evaluator, &projector);
	std::vector<std::string> results;
	

	// Different querying synonyms
	results = queryProcessor.evaluateQuery("assign a; Select a such that Parent(a, 2)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	results = queryProcessor.evaluateQuery("stmt s; Select s such that Parent(s, 4)");
	CPPUNIT_ASSERT_EQUAL(1, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("3"), 0);

	results = queryProcessor.evaluateQuery("variable v; Select v such that Parent(v, 4)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	results = queryProcessor.evaluateQuery("prog_line p; Select p such that Parent(p, \"7\")");
	CPPUNIT_ASSERT_EQUAL(1, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("6"), 0);
	
	results = queryProcessor.evaluateQuery("while w; Select w such that Parent(w, \"7\")");
	CPPUNIT_ASSERT_EQUAL(1, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("6"), 0);

	// More than one synonym participating in the relation
	results = queryProcessor.evaluateQuery("while w1, w2; Select w1 such that Parent(w1, w2)");
	CPPUNIT_ASSERT_EQUAL(1, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("3"), 0);

	results = queryProcessor.evaluateQuery("while w1, w2; Select w2 such that Parent(w1, w2)");
	CPPUNIT_ASSERT_EQUAL(1, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("6"), 0);

	results = queryProcessor.evaluateQuery("while w1, w2; stmt s; Select w1 such that Parent(w1, s)");
	CPPUNIT_ASSERT_EQUAL(2, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("3"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(1).compare("6"), 0);

	results = queryProcessor.evaluateQuery("while w1, w2; stmt s; Select s such that Parent(w1, s)");
	CPPUNIT_ASSERT_EQUAL(4, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("4"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(1).compare("5"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(2).compare("6"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(3).compare("7"), 0);

	results = queryProcessor.evaluateQuery("while w1, w2; assign a; Select a such that Parent(w1, a)");
	CPPUNIT_ASSERT_EQUAL(3, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("4"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(1).compare("5"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(2).compare("7"), 0);

	// Querying object does not appear in any constraints
	results = queryProcessor.evaluateQuery("while w1, w2; stmt s; Select s such that Parent(w1, w2)");
	CPPUNIT_ASSERT_EQUAL(8, (int)results.size());
	
}


// Test if Parent relationship can be evaluated correctly
void QueryProcessorWrapperTest::testEvaluateParentStarRelationship() {
	cout << "Test QueryprocessorWrapper evaluate parent star\n";
	PKBStubForParentTest pkbStubForParent;
	QueryExpressionParser *queryExpParser = new QueryExpressionParser(&pkbStubForParent);
	QueryPreprocessor preprocessor = QueryPreprocessor(queryExpParser);
	QueryEvaluator evaluator = QueryEvaluator(&pkbStubForParent);
	QueryProjector projector = QueryProjector();
	QueryProcessorWrapper queryProcessor = QueryProcessorWrapper(&preprocessor, &evaluator, &projector);
	std::vector<std::string> results;
	

	// Different querying synonyms
	results = queryProcessor.evaluateQuery("assign a; Select a such that Parent*(a, 2)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	results = queryProcessor.evaluateQuery("stmt s; Select s such that Parent*(s, 4)");
	CPPUNIT_ASSERT_EQUAL(1, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("3"), 0);

	results = queryProcessor.evaluateQuery("variable v; Select v such that Parent(v, 4)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	results = queryProcessor.evaluateQuery("prog_line p; Select p such that Parent*(p, \"7\")");
	CPPUNIT_ASSERT_EQUAL(2, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("3"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(1).compare("6"), 0);
	
	results = queryProcessor.evaluateQuery("while w; Select w such that Parent*(w, \"7\")");
	CPPUNIT_ASSERT_EQUAL(2, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("3"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(1).compare("6"), 0);

	// More than one synonym participating in the relation
	results = queryProcessor.evaluateQuery("while w1, w2; Select w1 such that Parent*(w1, w2)");
	CPPUNIT_ASSERT_EQUAL(1, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("3"), 0);

	results = queryProcessor.evaluateQuery("while w1, w2; Select w2 such that Parent*(w1, w2)");
	CPPUNIT_ASSERT_EQUAL(1, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("6"), 0);

	results = queryProcessor.evaluateQuery("while w1, w2; stmt s; Select w1 such that Parent*(w1, s)");
	CPPUNIT_ASSERT_EQUAL(2, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("3"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(1).compare("6"), 0);

	results = queryProcessor.evaluateQuery("while w1, w2; stmt s; Select s such that Parent*(w1, s)");
	CPPUNIT_ASSERT_EQUAL(4, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("4"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(1).compare("5"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(2).compare("6"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(3).compare("7"), 0);

	results = queryProcessor.evaluateQuery("while w1, w2; assign a; Select a such that Parent*(w1, a)");
	CPPUNIT_ASSERT_EQUAL(3, (int)results.size());
	CPPUNIT_ASSERT_EQUAL(results.at(0).compare("4"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(1).compare("5"), 0);
	CPPUNIT_ASSERT_EQUAL(results.at(2).compare("7"), 0);

	// Querying object does not appear in any constraints
	results = queryProcessor.evaluateQuery("while w1, w2; stmt s; Select s such that Parent*(w1, w2)");
	CPPUNIT_ASSERT_EQUAL(8, (int)results.size());

	results = queryProcessor.evaluateQuery("assign a1, a2; while w; Select w such that Parent*(a1, a2)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	// Query args are two non synonyms 
	results = queryProcessor.evaluateQuery("while w1, w2; stmt s; Select s such that Parent*(1, 2)");
	CPPUNIT_ASSERT_EQUAL(0, (int)results.size());

	results = queryProcessor.evaluateQuery("while w1, w2; stmt s; Select s such that Parent*(6, 7)");
	CPPUNIT_ASSERT_EQUAL(8, (int)results.size());

	results = queryProcessor.evaluateQuery("while w1, w2; stmt s; Select s such that Parent*(   \"6\"  , 7)");
	CPPUNIT_ASSERT_EQUAL(8, (int)results.size());
	
}